#include "listenevent.hpp"
#include "acceptiobuffer.hpp"
#include "singleton.hpp"
#include <netinet/tcp.h>

namespace oraysock5 {

ListenEvent::ListenEvent(std::shared_ptr<IPAddress> listen_addr,
    __socket_type type)
    : EventHandler()
{
    listen_addr_ = listen_addr;
    type_ = type;
    if (type_ == SOCK_STREAM) {
        init_tcp();
    } else if (type_ == SOCK_DGRAM) {
        init_udp();
    } else {
        THROW_EXCEPTION("不支持的socket_type:" + std::to_string(type_));
    }
}
ListenEvent::~ListenEvent()
{
    stop();
}

void ListenEvent::input(int listenfd)
{
    struct sockaddr_storage cliaddr;
    memset(&cliaddr, 0, sizeof(cliaddr));
    socklen_t socklen = sizeof(cliaddr);

    int newfd = accept(listenfd, (struct sockaddr*)&cliaddr, &socklen);
    if (newfd < 0) {
        return;
    }
    if (evutil_make_socket_nonblocking(listenfd) < 0) {
        evutil_closesocket(newfd);
        THROW_EXCEPTION("call evutil_make_socket_nonblocking");
    }
    std::shared_ptr<IPAddress> newfdAddr = std::make_shared<IPAddress>(&cliaddr);
    LOG_TRACE << "接收新的客户端 ip:" << newfdAddr->ip() << " port:" << newfdAddr->port();
    std::shared_ptr<AcceptIoBuffer> iobuf = std::make_shared<AcceptIoBuffer>(base(), newfd);
    std::shared_ptr<AcceptNewFd> apnewfd = accept_newfd(newfdAddr, iobuf).lock();
    iobuf->set_newfd(apnewfd);
}

bool ListenEvent::stop() const
{
    if (listenfd_ != -1)
        evutil_closesocket(listenfd_);
    return true;
}

void ListenEvent::pause()
{
    if (ispause_ == false) {
        base()->delevent(this);
        ispause_ = true;
    }
}

void ListenEvent::resume()
{
    if (ispause_) {
        base()->addevent(this, nullptr);
        ispause_ = false;
    }
}

void ListenEvent::init_tcp()
{
    listenfd_ = socket(listen_addr_->family(), SOCK_STREAM, IPPROTO_IP);
    if (listenfd_ == -1) {
        THROW_EXCEPTION("call socket");
    }

    if (evutil_make_socket_nonblocking(listenfd_) < 0) {
        evutil_closesocket(listenfd_);
        THROW_EXCEPTION("call evutil_make_socket_nonblocking");
    }
    if (evutil_make_socket_closeonexec(listenfd_) < 0) {
        evutil_closesocket(listenfd_);
        THROW_EXCEPTION("call evutil_make_socket_closeonexec");
    }
    int on = 1;
    if (setsockopt(listenfd_, SOL_SOCKET, SO_KEEPALIVE, (void*)&on, sizeof(on)) < 0) {
        evutil_closesocket(listenfd_);
        THROW_EXCEPTION("call setsockopt");
    }
    if (setsockopt(listenfd_, SOL_TCP, TCP_NODELAY, (void*)&on, sizeof(on)) != 0) {
        evutil_closesocket(listenfd_);
        THROW_EXCEPTION("call setsockopt");
    }
    if (evutil_make_listen_socket_reuseable(listenfd_) < 0) {
        evutil_closesocket(listenfd_);
        THROW_EXCEPTION("call evutil_make_listen_socket_reuseable");
    }

    if (bind(listenfd_, (const struct sockaddr*)listen_addr_->sockaddr(), sizeof(sockaddr_storage)) < 0) {
        evutil_closesocket(listenfd_);
        std::ostringstream err;
        err << "call bind:" << EVUTIL_SOCKET_ERROR() << " " << evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()) << " -";
        THROW_EXCEPTION(err.str());
    }

    if (listen(listenfd_, 128) < 0) {
        evutil_closesocket(listenfd_);
        THROW_EXCEPTION("call listen");
    }
    LOG_TRACE << "tcp listen ip:" << listen_addr_->ip() << " port:" << listen_addr_->port();
}

void ListenEvent::init_udp()
{
    listenfd_ = socket(listen_addr_->family(), SOCK_DGRAM, IPPROTO_IP);
    if (listenfd_ == -1) {
        THROW_EXCEPTION("call socket");
    }

    if (evutil_make_socket_nonblocking(listenfd_) < 0) {
        evutil_closesocket(listenfd_);
        THROW_EXCEPTION("call evutil_make_socket_nonblocking");
    }
    if (evutil_make_socket_closeonexec(listenfd_) < 0) {
        evutil_closesocket(listenfd_);
        THROW_EXCEPTION("call evutil_make_socket_closeonexec");
    }

    if (evutil_make_listen_socket_reuseable(listenfd_) < 0) {
        evutil_closesocket(listenfd_);
        THROW_EXCEPTION("call evutil_make_listen_socket_reuseable");
    }

    if (bind(listenfd_, (const struct sockaddr*)listen_addr_->sockaddr(), sizeof(sockaddr_storage)) < 0) {
        evutil_closesocket(listenfd_);
        std::ostringstream err;
        err << "call bind:" << EVUTIL_SOCKET_ERROR() << " " << evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()) << " -";
        THROW_EXCEPTION(err.str());
    }
    LOG_TRACE << "udp bind ip:" << listen_addr_->ip() << " port:" << listen_addr_->port();
}
}
